import React, { useEffect, useState } from "react";
import { Row, Col, message, Popconfirm, Button, Input } from "antd";

const drawLine = function(
  ctx,
  fromX,
  fromY,
  toX,
  toY,
  width = 1,
  color = "#1890ff"
) {
  ctx.save();
  ctx.beginPath();
  ctx.moveTo(fromX, fromY);
  ctx.lineTo(toX, toY);
  ctx.strokeStyle = color;
  ctx.lineWidth = width;
  ctx.stroke();
};

const drawArrow = function(
  ctx,
  fromX,
  fromY,
  toX,
  toY,
  theta = 30,
  headlen = 10,
  width = 1,
  color = "#000"
) {
  let angle = (Math.atan2(fromY - toY, fromX - toX) * 180) / Math.PI,
    angle1 = ((angle + theta) * Math.PI) / 180,
    angle2 = ((angle - theta) * Math.PI) / 180,
    topX = headlen * Math.cos(angle1),
    topY = headlen * Math.sin(angle1),
    botX = headlen * Math.cos(angle2),
    botY = headlen * Math.sin(angle2);
  ctx.save();
  ctx.beginPath();
  var arrowX, arrowY;
  ctx.moveTo(fromX, fromY);
  ctx.lineTo(toX, toY);
  arrowX = toX + topX;
  arrowY = toY + topY;
  ctx.moveTo(arrowX, arrowY);
  ctx.lineTo(toX, toY);
  arrowX = toX + botX;
  arrowY = toY + botY;
  ctx.lineTo(arrowX, arrowY);
  ctx.strokeStyle = color;
  ctx.lineWidth = width;
  ctx.stroke();
  // ctx.restore();
};

const defaultOptionConfig = [
  {
    name: "请选择节点对象",
    id: 0
  }
];
// 母本uuid；           样本uuid；      起点；           终点；时间校正偏移数；母本时差；时长固定3秒；比对结果（1匹配。0不匹配）；
// 2afb854078a3446a;2afb854078a3446a;1575517953096;1575517953169;148;0;3000;1
const defaultTopo = { "0-0": [] };

const CommonGraph = ({
  warnInfo,
  collectoies,
  Node,
  hasButton,
  onDelete,
  chainName,
  onSave,
  onCancel,
  id,
  options,
  topo,
  tierWidth,
  cavasHeight,
  marginTop,
  justify
}) => {
  const canvasId = `canvas-${id}`;
  const [initState] = useState({ topo, chainName });
  const [canEditor, setCanEditor] = useState(false); //当前链路是否可编辑
  const [selfChainName, setSelfChainName] = useState(chainName);
  const [Topo, setTopo] = useState(topo);
  /**
   * 所有针对节点的topo关系的修改都在Topo对象上进行，而不是在groupedByTire（一个依照节点层级分类之后的二维数组）上进行,
   * Topo对象中的属性名称按照`${id}-${trieIndex}`（ID-层级）构造，属性名称对应一个节点,值为一个数组，包含该节点在下一层级的子节点。
   */
  const [groupedByTire, setGroupedByTire] = useState([]);
  const [Ctx, setCtx] = useState(null);
  const [isDown, setIsDown] = useState(false);
  const [startPos, setStart] = useState(0);
  const [Path, setPath] = useState(0);

  const groupByTire = topo => {
    let group = Object.keys(topo).reduce((acc, cur, curIndex, arr) => {
      const curTire = cur.split("-")[1];
      if (!acc[curTire]) {
        acc[curTire] = [];
      }
      acc[curTire].unshift(cur.split("-")[0]);
      return acc;
    }, []);
    return group;
  };

  useEffect(() => {
    /**
     * groupedData:arrary 依照层级分组的二维数组，index为层级
     * Topo:object 拓扑关系
     */
    const groupedData = groupByTire(Topo);
    setGroupedByTire(groupedData);
  }, [Topo]);

  const drawArrows = (groupedData, canvasId) => {
    if (Ctx) {
      Ctx.clearRect(0, 0, groupedByTire.length * tierWidth, cavasHeight); //每次绘画箭头前需要清除画布
    }

    const c = document.querySelector(`#${canvasId}`);
    let ctx = c.getContext("2d");
    setTimeout(() => {
      groupedData.forEach((col, trieIndex) => {
        const Col = document.querySelectorAll(`#${canvasId}-wraper .col`)[
          trieIndex
        ];
        const leftOfCol = Col.offsetLeft;
        console.log(Topo, id, Col);
        Col &&
          col.forEach((id, index) => {
            let originSel = Col.querySelectorAll(`.sel`)[index];
            const leftOfSel = originSel.offsetLeft;
            const widthOfSel = originSel.offsetWidth;
            const heightOfSel = originSel.offsetHeight;
            const topOfSel = originSel.offsetTop;
            const origin = {
              x: leftOfCol + leftOfSel + widthOfSel,
              y: topOfSel + heightOfSel / 2
            };
            const targetCol = document.querySelectorAll(
              `#${canvasId}-wraper .col`
            )[trieIndex + 1];
            if (!targetCol) return;
            const targetLeftOffset = targetCol.offsetLeft;
            let middlePoint = {
              x: origin.x + (targetLeftOffset - origin.x) / 2,
              y: origin.y
            };
            const targets = Topo[`${id}-${trieIndex}`];
            !!targets.length && drawLine(ctx, origin.x, origin.y, middlePoint.x, middlePoint.y);
            targets &&
              targets.forEach((id, index) => {
                const leftOfTargetCol = targetCol.offsetLeft; //目标Col的left
                let targetIndex = groupedData[trieIndex + 1].findIndex(el => {
                  return Number(el) === id;
                }); //找到targets中的id在groupedData中的索引，因为dom是按照groupedData中的索引为顺序渲染的
                const targetSel = targetCol.querySelectorAll(`.sel`)[
                  targetIndex
                ];
                if (!targetSel) {
                  return;
                }
                const leftOfSel = targetSel.offsetLeft;
                const heightOfSel = targetSel.offsetHeight;
                const topOfSel = targetSel.offsetTop;
                const target = {
                  x: leftOfTargetCol + leftOfSel,
                  y: topOfSel + heightOfSel / 2
                };
                if (target.y !== origin.y) {
                  drawLine(
                    ctx,
                    middlePoint.x,
                    middlePoint.y,
                    middlePoint.x,
                    target.y
                  );
                  drawArrow(
                    ctx,
                    middlePoint.x,
                    target.y,
                    target.x,
                    target.y,
                    20,
                    10,
                    1,
                    "#1890ff"
                  );
                  return;
                }

                drawArrow(
                  ctx,
                  middlePoint.x,
                  middlePoint.y,
                  target.x,
                  target.y,
                  20,
                  10,
                  1,
                  "#1890ff"
                );
                // Ctx.moveTo(origin.x, origin.y)
                // Ctx.lineTo(target.x, target.y)
                // Ctx.stroke()
              });
          });
      });
    }, 0);
    return ctx; //将每次绘画的画布返回，替换Ctx
  };

  /**
   *
   * @param {number} trieIndex 待删除节点所在层级
   * @param {number} id 被删除节点的id（住键）
   */
  const deleteNode = (trieIndex, id) => {
    //所有针对节点的topo关系的修改都在Topo对象上进行，使用setTopo修改，而不是在groupedByTire（一个依照节点层级分类之后的二维数组）上进行。
    setTopo(oldTopo => {
      // oldTopo,节点topo关系
      if (trieIndex === 0) {
        message.error("根节点不可删除！");
        return oldTopo;
      } else if (oldTopo[`${id}-${trieIndex}`].length) {
        message.error("请先删除该节点下的子节点！");
        return oldTopo;
      }

      Object.keys(oldTopo)
        .filter(key => Number(key.split("-")[1]) === trieIndex - 1)
        .forEach(key => {
          oldTopo[key] = oldTopo[key].filter(el => el !== id); //清楚上一级节点id为id的节点
        });
      delete oldTopo[`${id}-${trieIndex}`];

      const newTopo = JSON.parse(JSON.stringify(oldTopo));
      return newTopo;
    });
  };

  /**
   *
   * @param {number} trieIndex 节点所在层级
   * @param {number} id 节点id
   */

  const addNode = (trieIndex, id) => {
    const sameTrieNodes = Object.keys(Topo)
      .filter(key => Number(key.split("-")[1]) === trieIndex + 1)
      .map(key => Number(key.split("-")[0]));
    if (sameTrieNodes.includes(0)) {
      message.error("同层级不可包含相同节点！");
      return;
    }
    if (id === 0) {
      message.error("请先为当前节点选择名称！");
      return;
    }

    //所有针对节点的topo关系的修改都在Topo对象上进行，使用setTopo修改，而不是在groupedByTire（一个依照节点层级分类之后的二维数组）上进行。
    setTopo(oldTopo => {
      const newTopo = JSON.parse(JSON.stringify(oldTopo));
      newTopo[`${id}-${trieIndex}`].push(0);
      newTopo[`${0}-${trieIndex + 1}`] = [];

      return newTopo;
    });
  };

  useEffect(() => {
    /**
     * groupedByTire:fuc  画箭头的函数
     * canvasId：string  画布id
     * Topo：object 拓扑关系
     * ctx： canvas上下文
     */
    const ctx = drawArrows(groupedByTire, canvasId);
    setCtx(ctx); //替换掉Ctx
  }, [groupedByTire, canvasId]);

  /**
   *
   * @param {number} newValue 受改变的节点的新的id
   * @param {numerb} trieIndex 受改变的节点的所在层级
   * @param {number} id 受改变的节点的之前的id
   */
  const changeTopo = (newValue, trieIndex, id) => {
    /**
     * 若节点为首节点，添加一个新的首节点，删除之前的首节点
     */
    if (trieIndex === 0) {
      setTopo(oldTopo => {
        oldTopo[`${newValue}-0`] = JSON.parse(
          JSON.stringify(oldTopo[`${id}-0`])
        );
        delete oldTopo[`${id}-0`];
        let newTopo = JSON.parse(JSON.stringify(oldTopo));
        return newTopo;
      });
    } else {
      setTopo(oldTopo => {
        /**
         * 这里替换属性名称的方式和上面不一样，不能用先添加后delete的方法，否则对象中属性的顺序会变化,只能执行节点属性名称replace替换操作
         */
        const femaleTrieIndex = trieIndex - 1; //母本的层级trieIndex:number为当前节点的上一级
        // let oldfemaleId = Object.keys(oldTopo).find(key => Number(key.split('-')[1]) === femaleTrieIndex && oldTopo[key].includes(id)).split('-')[0]

        const newTopo = JSON.parse(
          JSON.stringify(oldTopo).replace(
            `${id}-${trieIndex}`,
            `${newValue}-${trieIndex}`
          )
        );
        let female = Object.keys(newTopo).find(
          key =>
            Number(key.split("-")[1]) === femaleTrieIndex &&
            newTopo[key].includes(id)
        );

        const pos = newTopo[female].findIndex(sampleId => sampleId === id);
        newTopo[female][pos] = newValue;
        return JSON.parse(JSON.stringify(newTopo));
      });
    }
  };

  /**
   *
   * @param {number} trieIndex 节点所在层级
   * @param {number} id 节点id
   *
   * return boleen 按照同一层级节点不可含有相同id的节点，来返回Select中Option的disabled的值。
   */

  const optionIsDisabled = (trieIndex, id) => {
    const sameTrieNodes = Object.keys(Topo)
      .filter(key => Number(key.split("-")[1]) === trieIndex)
      .map(key => Number(key.split("-")[0]));
    if (sameTrieNodes.includes(id)) {
      return true;
    } else {
      return false;
    }
  };

  const wrapOnMouseDown = event => {
    if (/^canvas-.*$/.test(event.target.id)) {
      const e = event || window.event;
      let start = e.clientX;
      setStart(start - Path);
      setIsDown(true);
    }
    if (/^ant-col/.test(event.target.className)) {
      console.log("event.target", event.target);
    }
  };
  const wrapOnMouseMove = event => {
    if (isDown) {
      let wraper = document.querySelector(`#${canvasId}-wraper`);
      const e = event || window.event;
      const pos = e.clientX;
      const path = pos - startPos;
      setPath(path);
      wraper.style.left = path + "px";
      // console.log('wrapOnMouseMove')
    }
  };
  const wrapOnMouseUp = () => {
    setIsDown(false);
  };
  const exchangeIndex = (trieIndex, dragIndex, hoverIndex) => {
    const drageId = groupedByTire[trieIndex][dragIndex];
    const hoverId = groupedByTire[trieIndex][hoverIndex];
    let keys = Object.keys(Topo);
    let dragIndexInKeys = keys.findIndex(
      key => key === `${drageId}-${trieIndex}`
    );
    let hoverIndexInKeys = keys.findIndex(
      key => key === `${hoverId}-${trieIndex}`
    );
    let intermediate = keys[dragIndexInKeys];
    keys[dragIndexInKeys] = keys[hoverIndexInKeys];
    keys[hoverIndexInKeys] = intermediate;
    setTopo(oldTopo => {
      let newTopo = keys.reduce((acc, key, keyIndex, arr) => {
        acc[key] = Topo[key];
        return acc;
      }, {});
      return newTopo;
    });
  };
  const chainNameChange = e => {
    setSelfChainName(e.target.value);
  };
  const handleCancel = () => {
    setTopo(initState.topo);
    setSelfChainName(initState.chainName);
    setCanEditor(false);
    onCancel(); //porps.onCancel
  };
  const handleSave = (Topo, { chainName: selfChainName, id }) => {
    onSave(Topo, { chainName: selfChainName, id }, canEditor => {
      setCanEditor(canEditor);
    });
  };
  const span = 6;
  const gutter = span * groupedByTire.length;
  const width =
    justify === "center" ? "100%" : groupedByTire.length * tierWidth + "px";
  return (
    <div style={{overflowX: 'auto', overflowY: 'hidden'}}>
      <Row type="flex" justify="space-between">
        {hasButton ? (
          canEditor ? (
            <div>
              <Popconfirm
                placement="right"
                title="是否取消编辑？此操作将清空所有操作"
                onConfirm={handleCancel}
                okText="Yes"
                cancelText="No"
              >
                <Button type="danger">取消</Button>
              </Popconfirm>
              <Popconfirm
                placement="right"
                title="是否保存该链路？"
                onConfirm={() => {
                  handleSave(Topo, { chainName: selfChainName, id });
                }}
                okText="Yes"
                cancelText="No"
              >
                <Button type="primary" style={{ margin: "0 20px" }}>
                  保存
                </Button>
              </Popconfirm>
              <Popconfirm
                placement="right"
                title="是否确认删除当前链路？"
                onConfirm={() => {
                  onDelete(id);
                }}
                okText="Yes"
                cancelText="No"
              >
                <Button
                  type="danger"
                  style={{ position: "absolute", right: "10px" }}
                >
                  删除链路
                </Button>
              </Popconfirm>
              <div
                style={{ position: "absolute", zIndex: "2", marginTop: "10px" }}
              >
                <Input
                  addonBefore="链路名称:"
                  defaultValue={chainName}
                  onChange={chainNameChange}
                ></Input>
              </div>
            </div>
          ) : (
            <Button type="primary" onClick={() => setCanEditor(true)}>
              编辑
            </Button>
          )
        ) : (
          ""
        )}
      </Row>
      <div
        style={{ position: "relative", width: width, marginTop: marginTop }}
        // onMouseDown={wrapOnMouseDown}
        // onMouseMove={wrapOnMouseMove}
        // onMouseUp={wrapOnMouseUp}
        id={`${canvasId}-wraper`}
      >
        <canvas
          id={canvasId}
          width={`${groupedByTire.length * tierWidth}`}
          height={cavasHeight}
          style={{ position: "absolute", left: "0px" }}
        ></canvas>
        <Row
          type="flex"
          justify={justify}
          style={{ flexWrap: "nowrap" }}
          gutter={gutter}
        >
          {groupedByTire.map((group, trieIndex) => {
            return (
              <Col
                className={`col`}
                key={trieIndex}
                style={{
                  display: "flex",
                  flexDirection: "column",
                  justifyContent: "space-around",
                  alignItems: "flex-start"
                }}
              >
                {group.map((node, index) => {
                  let uuid =
                    collectoies.length &&
                    collectoies.find(item => Number(item.id) === Number(node))
                      .uuid;
                  let femalUUID = null;
                  if (trieIndex !== 0) {
                    let uppers = Object.keys(Topo).filter(
                      key => Number(key.split("-")[1]) === trieIndex - 1
                    );
                    let female = uppers.find(key =>
                      Topo[key].includes(Number(node))
                    );
                    femalUUID =
                      collectoies.length &&
                      collectoies.find(
                        item => Number(item.id) === Number(female.split("-")[0])
                      ).uuid;
                  }
                  const nodePorps = {
                    exchangeIndex, //拖拽节点的回调函数
                    index, //节点在当前层级的索引
                    canEditor, //是否可以编辑
                    groupedByTire, //二维数组（按照节点层级排列的二维数组）
                    addNode, //添加节点的回调函数
                    deleteNode, //删除节点的回调函数
                    optionIsDisabled, //判断Select的option是否被可选（被禁用）的函数
                    changeTopo, //改变Topo关系的回调函数
                    trieIndex, //节点的所在层级
                    id: Number(node), //节点的id
                    options, //Select的选单
                    warnInfo, //异常信息
                    uuid, //uuid
                    femalUUID //母本uuid
                  };
                  return (
                    <Node
                      key={`${node}-${trieIndex}-${index}`}
                      {...nodePorps}
                    ></Node>
                  );
                })}
              </Col>
            );
          })}
        </Row>
      </div>
    </div>
  );
};

CommonGraph.defaultProps = {
  options: defaultOptionConfig, //select组件选项
  topo: defaultTopo, //节点拓扑关系
  canvasId: "myCanvas",
  hasButton: true,
  collectoies: [],
  warnInfo: {},
  tierWidth: 400,
  cavasHeight: 5000,
  marginTop: "50px",
  justify: "space-between"
};

export default React.memo(CommonGraph);
